/**
 *Copyright [2008-2009] [dennis zhuang]
 *Licensed under the Apache License, Version 2.0 (the "License");
 *you may not use this file except in compliance with the License.
 *You may obtain a copy of the License at
 *             http://www.apache.org/licenses/LICENSE-2.0
 *Unless required by applicable law or agreed to in writing,
 *software distributed under the License is distributed on an "AS IS" BASIS,
 *WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 *either express or implied. See the License for the specific language governing permissions and limitations under the License
 */

package com.google.code.yanf4j.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.impl.StandardSocketOption;
import com.google.code.yanf4j.nio.impl.SocketChannelController;

/**
 * Controller for tcp server
 * 
 * @author dennis
 */
public class TCPController extends SocketChannelController {

	private ServerSocketChannel serverSocketChannel;

	/**
	 * Accept backlog queue size
	 * 
	 * backlog参数是指等待处理的连接请求的最大个数。如果是同时有5个客户端发出链接请求，
	 * 而backlog值为2，只有其中两个能够进入等待处理队列，其他三个返回连接失败错误。
	 * 
	 * 参考链接：http://blog.csdn.net/huang_xw/article/details/7338487
	 */
	private int backlog = 500; // default 500

	/**
	 * 以下3 个参数表示网络传输数据的 3 个指标.
	 * 参数 connectionTime: 表示用最少时间建立连接;
	 * 参数 latency: 表示最小延迟;
	 * 参数 bandwidth: 表示最高带宽;
	 *  
	 * setPerformancePreferences() 方法用来设定这 3 项指标之间的相对重要性. 
	 * 可以为这些参数赋予任意的整数, 这些整数之间的相对大小就决定了相应参数的相对重要性.
	 * 例如, 如果参数 connectionTime 为 2, 参数 latency 为 1, 而参数bandwidth 为 3, 
	 * 就表示最高带宽最重要, 其次是最少连接时间, 最后是最小延迟.
	 * 
	 * 更多信息参考：http://hi.baidu.com/wnwllfeztkfknwr/item/48a1385089c3dfc89e2667b9
	 * 
	 * 
	 */
	private int connectionTime, latency, bandwidth;

	public TCPController() {
		super();
	}

	public TCPController(Configuration configuration) {
		super(configuration, null, null);

	}

	public TCPController(Configuration configuration, CodecFactory codecFactory) {
		super(configuration, null, codecFactory);
	}

	public TCPController(Configuration configuration, Handler handler, CodecFactory codecFactory) {
		super(configuration, handler, codecFactory);
	}

	/**
	 * 启动监听并设置socket相关参数.
	 */
	@Override
	protected void doStart() throws IOException {
		serverSocketChannel = ServerSocketChannel.open();

		/**
		 * 设置socket调用InputStream读数据的超时时间,以毫秒为单位.
		 */
		serverSocketChannel.socket().setSoTimeout(this.soTimeout);
		if (connectionTime != 0 || latency != 0 || bandwidth != 0) {
			serverSocketChannel.socket().setPerformancePreferences(connectionTime, latency, bandwidth);
		}

		/**
		 * 设置为非阻塞模式.
		 */
		serverSocketChannel.configureBlocking(false);

		/**
		 * SO_REUSEADDR 表示是否允许重用 Socket 所绑定的本地地址. 
		 */
		if (socketOptions.get(StandardSocketOption.SO_REUSEADDR) != null) {
			serverSocketChannel.socket()
					.setReuseAddress(
							StandardSocketOption.SO_REUSEADDR.type().cast(
									socketOptions.get(StandardSocketOption.SO_REUSEADDR)));
		}

		/**
		 * 设置接收缓冲区
		 */
		if (this.socketOptions.get(StandardSocketOption.SO_RCVBUF) != null) {
			this.serverSocketChannel.socket().setReceiveBufferSize(
					StandardSocketOption.SO_RCVBUF.type().cast(this.socketOptions.get(StandardSocketOption.SO_RCVBUF)));

		}

		/**
		 * 为设置绑定地址则绑定到本地.
		 */
		if (this.localSocketAddress != null) {
			this.serverSocketChannel.socket().bind(this.localSocketAddress, this.backlog);
		} else {
			this.serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 0), this.backlog);
		}

		setLocalSocketAddress((InetSocketAddress) this.serverSocketChannel.socket().getLocalSocketAddress());

		/**
		 * 将channel注册到selector
		 * 
		 * 注意，附件设置的是null
		 * 
		 * darkmi@2013/10/30
		 * 
		 */
		this.selectorManager.registerChannel(this.serverSocketChannel, SelectionKey.OP_ACCEPT, null);
	}

	/**
	 * 有客户端连接上来则进行处理.
	 */
	@Override
	public void onAccept(SelectionKey selectionKey) throws IOException {

		if (!this.serverSocketChannel.isOpen()) {
			/**
			 * cancel()方法用于（永久性的）注销该键，并将其放入选择器的注销集（cancel set）中。
			 * 在下一次点采用select方法时，这些键将从该选择器的所有键集中移除，其关联的信道也将
			 * 不再被监听，除非它又重新注册。
			 */
			selectionKey.cancel();
			return;
		}

		SocketChannel sc = null;
		try {
			sc = this.serverSocketChannel.accept();
			if (sc != null) {
				configureSocketChannel(sc);
				Session session = buildSession(sc);
				// enable read
				this.selectorManager.registerSession(session, EventType.ENABLE_READ);
				session.start();
				super.onAccept(selectionKey); // for statistics
			} else {
				log.debug("Accept fail");
			}
		} catch (IOException e) {
			closeAcceptChannel(selectionKey, sc);
			log.error("Accept connection error", e);
			notifyException(e);
		}
	}

	/**
	 * 
	 * @param sk
	 * @param sc
	 * @throws IOException
	 * @throws SocketException
	 */
	private void closeAcceptChannel(SelectionKey sk, SocketChannel sc) throws IOException, SocketException {
		if (sk != null) {
			sk.cancel();
		}
		if (sc != null) {
			sc.socket().setSoLinger(true, 0); // await TIME_WAIT status
			sc.socket().shutdownOutput();
			sc.close();
		}
	}

	public void closeChannel(Selector selector) throws IOException {
		if (this.serverSocketChannel != null) {
			this.serverSocketChannel.close();
		}
	}

	public void unbind() throws IOException {
		stop();
	}

	/*============Setters And Getters ============*/

	public int getBacklog() {
		return this.backlog;
	}

	public void setBacklog(int backlog) {
		if (isStarted()) {
			throw new IllegalStateException();
		}
		if (backlog < 0) {
			throw new IllegalArgumentException("backlog<0");
		}
		this.backlog = backlog;
	}

	public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
		this.connectionTime = connectionTime;
		this.latency = latency;
		this.bandwidth = bandwidth;
	}

}